package xapollo

import (
	"context"
	"fmt"
	"gitee.com/go-mid/infra/xobserver"
	"strconv"
	"strings"

	"github.com/ZhengHe-MD/agollo/v4"
	"github.com/ZhengHe-MD/properties"
	"github.com/opentracing/opentracing-go"

	"gitee.com/go-mid/infra/xconfig"
	"gitee.com/go-mid/infra/xlog"
)

const (
	envApolloCluster            = "APOLLO_CLUSTER"
	envApolloHostPort           = "APOLLO_HOST_PORT"
	defaultCluster              = "default"
	defaultHostPort             = "apollo-meta.ibanyu.com:30002"
	defaultCacheDir             = "/tmp/sconfcenter"
	defaultNamespaceApplication = "application"
)

type apolloDriver struct{}

type ApolloConfigCenter struct {
	conf *agollo.Conf
	ag   *agollo.Agollo
}

// SetCluster set apollo center cluster, default value "default"
func SetCluster(cluster string) xconfig.Option {
	return func(center xconfig.ConfigCenter) {
		if apCenter, ok := center.(*ApolloConfigCenter); ok {
			apCenter.SetCluster(cluster)
		} else {
			fmt.Printf("SetCluster assert apollo center type err\n")
		}
	}
}

// SetCacheDir set apollo cache dir, default value "/tmp/sconfcenter"
func SetCacheDir(cacheDir string) xconfig.Option {
	return func(center xconfig.ConfigCenter) {
		if apCenter, ok := center.(*ApolloConfigCenter); ok {
			apCenter.SetCacheDir(cacheDir)
		} else {
			fmt.Printf("SetCacheDir assert apollo center type err\n")
		}
	}
}

// SetIPHost set apollo remote host, default value "apollo-meta.ibanyu.com:30002"
func SetIPHost(ipHost string) xconfig.Option {
	return func(center xconfig.ConfigCenter) {
		if apCenter, ok := center.(*ApolloConfigCenter); ok {
			apCenter.SetIPHost(ipHost)
		} else {
			fmt.Printf("SetIPHost assert apollo center type err\n")
		}
	}
}

type ApolloConfigCenterConf struct {
	agollo.Conf
}

func NewApolloConfigCenter(ctx context.Context, conf *ApolloConfigCenterConf) (*ApolloConfigCenter, error) {
	span, ctx := opentracing.StartSpanFromContext(ctx, "apolloDriver.New")
	defer span.Finish()
	fun := "apolloDriver.New-->"
	agollo.SetLogger(xlog.GetInfoLogger())
	center := &ApolloConfigCenter{
		conf: &conf.Conf,
	}

	center.ag = agollo.NewAgollo(center.conf)

	xlog.Infof(ctx, "%s start agollo with conf:%v", fun, center.conf)

	if err := center.ag.Start(); err != nil {
		xlog.Errorf(ctx, "%s agollo starts err:%v", fun, err)
		return nil, err
	} else {
		xlog.Infof(ctx, "%s agollo starts succeed:%v", fun, err)
	}
	center.ag.StartWatchUpdate()

	return center, nil
}

func (ap *ApolloConfigCenter) RegisterObserver(ctx context.Context, namespace string, observer *xobserver.ConfigObserver) func() {
	// 注册时 启动监听
	observer.StartWatch(ctx)
	return ap.ag.RegisterObserver(&agolloObserver{observer})
}

func (ap *ApolloConfigCenter) Stop(ctx context.Context) error {
	span, _ := opentracing.StartSpanFromContext(ctx, "ApolloConfigCenter.Stop")
	defer span.Finish()
	return ap.ag.Stop()
}

func (ap *ApolloConfigCenter) SubscribeNamespaces(ctx context.Context, namespaceNames []string) error {
	span, _ := opentracing.StartSpanFromContext(ctx, "ApolloConfigCenter.SubscribeNamespaces")
	defer span.Finish()
	return ap.ag.SubscribeToNamespaces(namespaceNames...)
}

func (ap *ApolloConfigCenter) GetString(ctx context.Context, namespace, key string) (string, bool) {
	span, _ := opentracing.StartSpanFromContext(ctx, "ApolloConfigCenter.GetString")
	defer span.Finish()

	return ap.ag.GetStringWithNamespace(namespace, key)
}

func (ap *ApolloConfigCenter) GetBool(ctx context.Context, namespace, key string) (bool, bool) {
	span, _ := opentracing.StartSpanFromContext(ctx, "ApolloConfigCenter.GetBool")
	defer span.Finish()

	return ap.ag.GetBoolWithNamespace(namespace, key)
}

func (ap *ApolloConfigCenter) GetInt(ctx context.Context, namespace, key string) (int, bool) {
	span, _ := opentracing.StartSpanFromContext(ctx, "ApolloConfigCenter.GetInt")
	defer span.Finish()

	return ap.ag.GetIntWithNamespace(namespace, key)
}

func (ap *ApolloConfigCenter) GetFloat64(ctx context.Context, namespace, key string) (float64, bool) {
	span, _ := opentracing.StartSpanFromContext(ctx, "ApolloConfigCenter.GetFloat64")
	defer span.Finish()

	return ap.ag.GetFloat64WithNamespace(namespace, key)
}

func (ap *ApolloConfigCenter) GetInt64(ctx context.Context, namespace, key string) (int64, bool) {
	span, _ := opentracing.StartSpanFromContext(ctx, "ApolloConfigCenter.GetInt64")
	defer span.Finish()
	val, ok := ap.ag.GetIntWithNamespace(namespace, key)
	return int64(val), ok
}

func (ap *ApolloConfigCenter) GetInt32(ctx context.Context, namespace, key string) (int32, bool) {
	span, _ := opentracing.StartSpanFromContext(ctx, "ApolloConfigCenter.GetInt32")
	defer span.Finish()
	val, ok := ap.ag.GetIntWithNamespace(namespace, key)
	return int32(val), ok
}

func (ap *ApolloConfigCenter) GetIntSlice(ctx context.Context, namespace, keyPrefix string) ([]int, bool) {
	fun := "ApolloConfigCenter.GetIntSlice -->"
	span, _ := opentracing.StartSpanFromContext(ctx, "ApolloConfigCenter.GetIntSlice")
	defer span.Finish()

	ks := ap.GetAllKeys(ctx, namespace)
	targetMap, err := ap.getSliceIdxMap(ctx, ks, keyPrefix)
	if err != nil {
		xlog.Errorf(ctx, "%s agollo atoi err:%v", fun, err)
		return nil, false
	}
	return ap.getIntSlice(ctx, namespace, keyPrefix, targetMap)
}

func (ap *ApolloConfigCenter) GetInt64Slice(ctx context.Context, namespace, keyPrefix string) ([]int64, bool) {
	fun := "ApolloConfigCenter.GetInt64Slice -->"
	span, _ := opentracing.StartSpanFromContext(ctx, "ApolloConfigCenter.GetInt64Slice")
	defer span.Finish()

	ks := ap.GetAllKeys(ctx, namespace)
	targetMap, err := ap.getSliceIdxMap(ctx, ks, keyPrefix)
	if err != nil {
		xlog.Errorf(ctx, "%s agollo atoi err:%v", fun, err)
		return nil, false
	}
	return ap.getInt64Slice(ctx, namespace, keyPrefix, targetMap)
}

func (ap *ApolloConfigCenter) GetInt32Slice(ctx context.Context, namespace, keyPrefix string) ([]int32, bool) {
	fun := "ApolloConfigCenter.GetInt32Slice -->"
	span, _ := opentracing.StartSpanFromContext(ctx, "ApolloConfigCenter.GetInt32Slice")
	defer span.Finish()

	ks := ap.GetAllKeys(ctx, namespace)
	targetMap, err := ap.getSliceIdxMap(ctx, ks, keyPrefix)
	if err != nil {
		xlog.Errorf(ctx, "%s agollo atoi err:%v", fun, err)
		return nil, false
	}
	return ap.getInt32Slice(ctx, namespace, keyPrefix, targetMap)
}

func (ap *ApolloConfigCenter) GetStringSlice(ctx context.Context, namespace, keyPrefix string) ([]string, bool) {
	fun := "ApolloConfigCenter.GetStringSlice -->"
	span, _ := opentracing.StartSpanFromContext(ctx, "ApolloConfigCenter.GetStringSlice")
	defer span.Finish()

	ks := ap.GetAllKeys(ctx, namespace)
	targetMap, err := ap.getSliceIdxMap(ctx, ks, keyPrefix)
	if err != nil {
		xlog.Errorf(ctx, "%s agollo atoi err:%v", fun, err)
		return nil, false
	}
	return ap.getStringSlice(ctx, namespace, keyPrefix, targetMap)
}

func (ap *ApolloConfigCenter) getSliceIdxMap(ctx context.Context, ks []string, keyPrefix string) (map[int]struct{}, error) {
	targetMap := make(map[int]struct{})
	for _, k := range ks {
		if strings.HasPrefix(k, keyPrefix) {
			karray := strings.Split(k, "[")
			if len(karray) != 2 {
				continue
			}
			if karray[0] == keyPrefix {
				kidxStr := strings.Split(karray[1], "]")
				kidx, err := strconv.Atoi(kidxStr[0])
				if err != nil {
					return targetMap, err
				}
				targetMap[kidx] = struct{}{}
			}
		}
	}
	return targetMap, nil
}

func (ap *ApolloConfigCenter) GetAllKeys(ctx context.Context, namespace string) []string {
	span, _ := opentracing.StartSpanFromContext(ctx, "ApolloConfigCenter.GetAllKeys")
	defer span.Finish()

	return ap.ag.GetAllKeys(namespace)
}

func (ap *ApolloConfigCenter) Unmarshal(ctx context.Context, namespace string, v interface{}) error {
	var kv = map[string]string{}

	ks := ap.GetAllKeys(ctx, namespace)
	for _, k := range ks {
		if v, ok := ap.GetString(ctx, namespace, k); ok {
			kv[k] = v
		}
	}

	return properties.UnmarshalKV(kv, v)
}

func (ap *ApolloConfigCenter) UnmarshalKey(ctx context.Context, namespace string, key string, v interface{}) error {
	var kv = map[string]string{}

	ks := ap.GetAllKeys(ctx, namespace)
	for _, k := range ks {
		if v, ok := ap.GetString(ctx, namespace, k); ok {
			kv[k] = v
		}
	}

	bs, err := properties.Marshal(&kv)
	if err != nil {
		return err
	}

	return properties.UnmarshalKey(key, bs, v)
}

func (ap *ApolloConfigCenter) getIntSlice(ctx context.Context, namespace, keyPrefix string, kMap map[int]struct{}) ([]int, bool) {
	fun := "ApolloConfigCenter.getIntSlice -->"

	arrayLen := len(kMap)
	if arrayLen == 0 {
		return nil, false
	}

	kSlice := make([]int, arrayLen)

	for k, _ := range kMap {
		val, ok := ap.ag.GetInt(keyPrefix + "[" + strconv.Itoa(k) + "]")
		if !ok {
			xlog.Errorf(ctx, "%s get error key: %s", fun, keyPrefix+"["+strconv.Itoa(k)+"]")
			return nil, false
		}
		kSlice[k] = val
	}

	return kSlice, true
}

func (ap *ApolloConfigCenter) getInt64Slice(ctx context.Context, namespace, keyPrefix string, kMap map[int]struct{}) ([]int64, bool) {
	fun := "ApolloConfigCenter.getInt64Slice -->"

	arrayLen := len(kMap)
	if arrayLen == 0 {
		return nil, false
	}

	kSlice := make([]int64, arrayLen)

	for k, _ := range kMap {
		val, ok := ap.ag.GetInt(keyPrefix + "[" + strconv.Itoa(k) + "]")
		if !ok {
			xlog.Errorf(ctx, "%s get error key: %s", fun, keyPrefix+"["+strconv.Itoa(k)+"]")
			return nil, false
		}
		kSlice[k] = int64(val)
	}

	return kSlice, true
}

func (ap *ApolloConfigCenter) getInt32Slice(ctx context.Context, namespace, keyPrefix string, kMap map[int]struct{}) ([]int32, bool) {
	fun := "ApolloConfigCenter.getInt32Slice -->"

	arrayLen := len(kMap)
	if arrayLen == 0 {
		return nil, false
	}

	kSlice := make([]int32, arrayLen)

	for k, _ := range kMap {
		val, ok := ap.ag.GetInt(keyPrefix + "[" + strconv.Itoa(k) + "]")
		if !ok {
			xlog.Errorf(ctx, "%s get error key: %s", fun, keyPrefix+"["+strconv.Itoa(k)+"]")
			return nil, false
		}
		kSlice[k] = int32(val)
	}

	return kSlice, true
}

func (ap *ApolloConfigCenter) getStringSlice(ctx context.Context, namespace, keyPrefix string, kMap map[int]struct{}) ([]string, bool) {
	fun := "ApolloConfigCenter.getStringSlice -->"

	arrayLen := len(kMap)
	if arrayLen == 0 {
		return nil, false
	}

	kSlice := make([]string, arrayLen)

	for k, _ := range kMap {
		val, ok := ap.ag.GetString(keyPrefix + "[" + strconv.Itoa(k) + "]")
		if !ok {
			xlog.Errorf(ctx, "%s get error key: %s", fun, keyPrefix+"["+strconv.Itoa(k)+"]")
			return nil, false
		}
		kSlice[k] = val
	}

	return kSlice, true
}

func (ap *ApolloConfigCenter) SetCluster(cluster string) {
	ap.conf.Cluster = cluster
}
func (ap *ApolloConfigCenter) SetCacheDir(cacheDir string) {
	ap.conf.CacheDir = cacheDir
}
func (ap *ApolloConfigCenter) SetIPHost(ipHost string) {
	ap.conf.IP = ipHost
}
